#!/usr/bin/env python
# encoding: utf-8

import re 
#Necesitamos de expresiones regulares para facilitar el análisis sintáctico
import string

from constants import *
from conexion_cliente import *


def parsear(request, buffer_cliente):
    """ Primera etapa del análisis sintáctico. Estudia "request" en busca de eol.
    De no estar presente, se almacena la cadena en "buffer_cliente", a la espera
    de que el cliente complete el mensaje. Caso contrario se continua con las
    restantes etapas del análisis sintáctico del mensaje.

    PRE : {request es un string and buffer_cliente es un objeto de tipo Buffer}

    (comando, codigo_error) = parsear(request, buffer_cliente)

    POST: {( (la petición era válida and comando es un objeto de tipo Command que 
    representa a la petición hftp realizada) or (la petición no está completa and 
    comando == None) ) and codigo_error == code_ok) or (comando == None and codigo_error
    indica el error de sintaxis que tenía la petición estudiada)}
    
    TESTEOS 
    
    >>> b = Buffer()
    >>> (comando,codigo_error)=parsear("quit\\r\\n",b)
    >>> comando.get_command() == "quit"
    True
    >>> (comando,codigo_error)=parsear("get_file_listing\\r\\n",b)
    >>> comando.get_command() == "get_file_listing"
    True
    >>> (comando,codigo_error)=parsear("get_metadata asd\\r\\n",b)
    >>> comando.get_command() == "get_metadata"
    True
    >>> comando.get_argn(1) == "asd"
    True
    >>> (comando,codigo_error)=parsear("get_slice asd 1 2\\r\\n",b)
    >>> comando.get_command() == "get_slice"
    True
    >>> comando.get_argn(1) == "asd"
    True
    >>> comando.get_argn(2) == "1"
    True
    >>> comando.get_argn(3) == "2"
    True
    >>> (comando,codigo_error)=parsear("quit",b)
    >>> comando == None and codigo_error == code_ok
    True
    >>> b.devolver_mensaje() == "quit"
    True
    >>> b.vaciar()
    >>> (comando,codigo_error)=parsear("q",b)
    >>> comando == None and b.devolver_mensaje() == "q"
    True
    >>> (comando,codigo_error)=parsear("qu",b)
    >>> comando == None and b.devolver_mensaje() == "qu"
    True
    >>> (comando,codigo_error)=parsear("qui",b)
    >>> comando == None and b.devolver_mensaje() == "qui"
    True
    >>> (comando,codigo_error)=parsear("quit",b)
    >>> comando == None and b.devolver_mensaje() == "quit"
    True
    >>> (comando,codigo_error)=parsear("quit\\r",b)
    >>> comando == None and b.devolver_mensaje() == "quit\\r"
    True
    >>> (comando,codigo_error)=parsear("quit\\r\\n",b)
    >>> comando != None and b.devolver_mensaje() == ""
    True
    >>> comando.get_command() == "quit"
    True """
	
    # PRECONDICIÓN
    assert isinstance(request, str)
    assert isinstance(buffer_cliente, Buffer)
    
    indice_de_eol = request.find(eol)
    
    if indice_de_eol != -1:
        # request contiene eol. Tomamos el tramo inicial que contiene a eol,
        # almacenamos lo restante. El tramo inicial va de 0 a indice_de_eol+1.
        mensaje_con_eol = request[0: indice_de_eol + 2]
        assert mensaje_con_eol.endswith(eol)
        (comando, codigo_error) = continuar_parseo(mensaje_con_eol)
        if len(request) > indice_de_eol + 2:
            #request se extiende más allá de eol. Almacenamos la cadena restante.
            buffer_cliente.almacenar_mensaje(request[indice_de_eol + 2:])
    else:
        # request no contiene eol. Lo almacenamos enteramente para parsearlo
        # cuando recibamos la porción restante.
        buffer_cliente.almacenar_mensaje(request)
        comando = None
        codigo_error = code_ok
	
    # POSTCONDICIÓN
    assert ((isinstance(comando, Command) or comando == None) and \
        codigo_error == code_ok) or (comando == None and codigo_error != code_ok)
		
    return (comando, codigo_error)
		
""" Realiza un análisis sintáctico de un mensaje completo (i.e. termina con eol).
PRE : {request == s ++ eol, para cierto string s}

(comando, codigo_error) = continuar_parseo(request)

POST : {(request es una petición HFTP válida and comando es una instancia de 
Command que representa a la petición HFTP parseada and codigo_error == code_ok)
or (comando == None and codigo_error es el código del error de sintaxis cometido 
en la petición parseada)} """

def continuar_parseo(request):
    # PRECONDICIÓN
    assert request.endswith(eol)
    
    comando = None
    codigo_error = code_ok
    listo = False
    
    if request[0: -2].find('\n') == -1:
        request_dividida = string.split(request[0: -2])
        
        if not listo and len(request_dividida) >= 1 and \
            request_dividida[0] == "quit":
            (comando, codigo_error) = parsear_quit(request_dividida)
            listo = True
        elif not listo and len(request_dividida) >= 1 and \
            request_dividida[0] == "get_file_listing":
            (comando, codigo_error) = parsear_get_file_listing(request_dividida)
            listo = True
        elif not listo and len(request_dividida) >= 1 and \
            request_dividida[0] == "get_metadata":
            (comando, codigo_error) = parsear_get_metadata(request_dividida)
            listo = True
        elif not listo and len(request_dividida) >= 1 and \
            request_dividida[0] == "get_slice":
            (comando, codigo_error) = parsear_get_slice(request_dividida)
            listo = True
        elif not listo:
            #El comando no está en la lista de comandos aceptados
            codigo_error = invalid_command
    else:
        codigo_error = bad_eol
	
    # POSTCONDICIÓN
    assert (isinstance(comando, Command) and codigo_error == code_ok) or \
        (comando == None and codigo_error != code_ok)
			
    return (comando, codigo_error)
	
""" Determina si request es una petición QUIT sintácticamente correcta.
PRE :  {request es una lista de string and request[0] == "quit"}

(comando,codigo_error) = parsear_quit(request)

POST : {(request es una petición QUIT válida and comando es una instancia de 
Command que representa a una petición QUIT and codigo_error = code_ok) or
(request no es una petición QUIT válida and comando == None and 
codigo_error == invalid_arguments)} """

def parsear_quit(request):
    # PRECONDICIÓN
    assert request[0] == "quit"
    
    comando = None
    codigo_error = invalid_arguments
    
    if len(request) == 1:
        comando = Command(request, 1)
        codigo_error = code_ok
	
    # POSTCONDICIÓN
    assert (isinstance(comando, Command) and codigo_error == code_ok) or \
        (comando == None and codigo_error == invalid_arguments)
		
    return (comando, codigo_error)
	
""" Determina si request es una petición GET_FILE_LISTING sintácticamente 
correcta.

PRE :  {request es una lista de string and request[0] == "get_file_listing"}

(comando,codigo_error) = parsear_get_file_listing(request)

POST : {(request es una petición GET_FILE_LISTING válida and comando es una 
instancia de Command que representa a una petición GET_FILE_LISTING and 
codigo_error = code_ok) or (request no es una petición GET_FILE_LISTING válida and 
comando == None and codigo_error == invalid_arguments)} """

def parsear_get_file_listing(request):
    # PRECONDICIÓN
    assert request[0] == "get_file_listing"
    
    comando = None
    codigo_error = invalid_arguments
    
    if len(request) == 1:
        comando = Command(request, 1)
        codigo_error = code_ok
	
    # POSTCONDICIÓN
    assert (isinstance(comando, Command) and codigo_error == code_ok) or \
        (comando == None and codigo_error == invalid_arguments)
		
    return (comando, codigo_error)

""" Determina si request es una petición GET_METADATA sintácticamente 
correcta.

PRE :  {request es una lista de string and request[0] == "get_metadata"}

(comando, codigo_error) = parsear_get_metadata(request)

POST : {(request es una petición GET_METADATA válida and comando es una 
instancia de Command que representa a una petición GET_METADATA and 
codigo_error = code_ok) or (request no es una petición GET_METADATA válida and 
comando == None and codigo_error == invalid_arguments)} """

def parsear_get_metadata(request):
    # PRECONDICIÓN
    assert request[0] == "get_metadata"
	
    comando = None
    codigo_error = invalid_arguments
    
    # Debemos comprobar que el argumento es un nombre de archivo válido.
    # Por cuestiones de seguridad, chequeamos que no se esté refiriendo
    # a un archivo que comienza con "." (un archivo oculto).
    if len(request) == 2 and not request[1].startswith("."):
        comando = Command(request, 2)
        codigo_error = code_ok
	
    # POSTCONDICIÓN
    assert (isinstance(comando, Command) and codigo_error == code_ok) or \
        (comando == None and codigo_error != code_ok)
	
    return (comando, codigo_error)
	
""" Determina si request es una petición GET_SLICE sintácticamente 
correcta.

PRE :  {request es una lista de string and request[0] == "get_slice"}

(comando,codigo_error) = parsear_get_slice(request)

POST : {(request es una petición GET_SLICE válida and comando es una 
instancia de Command que representa a una petición GET_SLICE and 
codigo_error == code_ok) or (request no es una petición GET_SLICE válida and 
comando == None and codigo_error == invalid_arguments)} """

def parsear_get_slice(request):
    # PRECONDICIÓN
    assert request[0] == "get_slice"
    
    comando = None
    codigo_error = invalid_arguments
    
    if len(request) == 4:
        # Debemos comprobar que los argumentos tienen una forma válida.
        if not request[1].startswith("."):
            # El segundo y el tercer argumento (longitud y offset, respec.) deben
            # ser números. Para simplificar el chequeo, empleamos expresiones 
            # regulares.
            match = re.match("\d+",request[2])
            if match != None and match.group() == request[2]:
                # Entonces existe una cadena que es tramo inicial de request[2] y que
                # encaja con el patrón "\d+". Más aún, esa cadena es igual a request[2].
                # Por lo tanto request[2] es válido.
                match = re.match("\d+",request[3])
                if match != None and match.group() == request[3]:
                    # Mismo argumento que en el caso anterior. Entonces, los 3 argumentos
                    # tienen la forma correcta.
                    comando = Command(request, 4)
                    codigo_error = code_ok
	
    # POSTCONDICIÓN
    assert (isinstance(comando, Command) and codigo_error == code_ok) \
        or (comando == None and codigo_error == invalid_arguments)
		
    return (comando, codigo_error)

class Command(object):
    def __init__(self, lista=[], size=0):
        self.l = lista
        self.size = size
    
    def get_command(self):
        """ Devuelve el nombre del comando """
        return self.l[0]
    
    def get_argn(self,n):
        """ Devuelve el n-ésimo argumento (n>=1) """
        return self.l[n]
        
    def get_cmdlen(self):
        """ Devuelve la cantidad de argumentos de un comando. Se incluye el
        propio comando """
        return self.size